package com.alibaba.citrus.cr.unified.inventory.facade.service;

import com.alibaba.citrus.common.loader.Loader;
import com.alibaba.citrus.common.pair.Pair;
import com.alibaba.citrus.cr.unified.inventory.model.MnSapIf004DOForCheckWrapper;
import com.alibaba.citrus.cr.unified.inventory.model.MnSapIf004DetailForCheck;
import com.alibaba.citrus.cr.unified.inventory.model.MnSapIf004SDOForCheck;
import com.alibaba.citrus.cr.common.util.DateUtils;
import com.alibaba.citrus.cr.common.util.SAPRequestUtils;
import com.alibaba.citrus.cr.common.util.SapAndCrmFieldDealUtils;
import com.alibaba.citrus.trail.log.LogEntry;
import com.alibaba.citrus.trail.store.LogStore;
import com.alibaba.cz.base.tool.log.Log;
import com.alibaba.lattice2.epoch.sdk.model.Result;
import com.epoch.app.crunifiedinventory.logschecker.dto.ExternalSyncMaterialLogCheckerRequest;
import com.epoch.app.crunifiedinventory.logschecker.model.LogsChecker;
import com.epoch.app.crunifiedinventory.logschecker.service.LogsCheckerService;
import com.epoch.app.crunifiedinventory.model.dto.*;
import com.epoch.app.crunifiedinventory.service.CrUnifiedInventoryService;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author Chen.mu
 * @date 2022/1/12
 */
@Service
@Primary
public class LogsCheckerServiceImpl implements LogsCheckerService {
    Log log = Log.getLogger(LogsCheckerServiceImpl.class);
    @Resource
    CrUnifiedInventoryService otsUnifiedInventoryService;
    @Resource(name="logsCheckSyncTaskExecutor")
    ThreadPoolTaskExecutor inventorySyncTaskExecutor;
    @Override
    public ExternalSyncMaterialLogsCheckResult externalSyncMaterialLogChecker(ExternalSyncMaterialLogCheckerRequest request) {
        ExternalSyncMaterialLogsCheckResult result = new ExternalSyncMaterialLogsCheckResult();
        /**
         * 根据 物料凭证id+去零得货品外都id+工厂code+逻辑仓code+批次号+数量匹配出入库单
         * 再用出入库单匹配库存流水
         * 都匹配到 就算这条物料凭证过了
         */
        String dayTime = DateUtils.toDateStr(request.getStartTime(), "yyyy-MM-dd");
        String endTimeStr = DateUtils.toDateStr(request.getEndTime(), "yyyy-MM-dd");
        String starTime = dayTime + " 00:00:00";
        String endTime = endTimeStr + " 23:59:59";
         List<LogEntry> logEntries = getOutMaterialIds(starTime, endTime);
        Result<List<MaterialTypes>> materialResult = otsUnifiedInventoryService.getMaterialTyps();
        List<MaterialTypes> materialTypesList = materialResult.getResult();
        Map<String, MaterialTypes> materialTypes = materialTypesList.stream()
                .collect(Collectors.toMap(x -> (x.getCodeType() + x.getOutInType()), x -> x, (v1, v2) -> v2));
        HashMap<String,String> retryTraceIds=new HashMap<>();
        List<MnSapIf004SDOForCheck> mnSapIf004SDOForChecks = getSapIf004DOListAfterFilter(logEntries,retryTraceIds);

        List<MnSapIf004SDOForCheck> successList=new ArrayList<>();
        List<MnSapIf004SDOForCheck> failList=new ArrayList<>();
        //遍历物料明证主单
        result= checkMethod(mnSapIf004SDOForChecks, materialTypes,successList,failList);

        //如果物料凭证请求失败，并且是定时任务触发的， 重试重发一下物料凭证
        //0314 自动重试功能先注释掉不重试。  下次sit调试通过再发
        request.setRetry(true);
        if(request.getRetry()){
            failList.stream().forEach(x->{
                for(MnSapIf004SDOForCheck mnSapIf004SDOForCheck: failList) {
                    String outMaterialId=mnSapIf004SDOForCheck.getOutMaterialCertificateId();
                    SAPRequestUtils.retryExteralMatrial(outMaterialId,
                            retryTraceIds.get(outMaterialId));
                }
            });
        }
        result.setReqNum(mnSapIf004SDOForChecks.size());
        String message = "简报查询日期:"+dayTime+"-"+endTimeStr
                + "物料凭证请求数:" + result.getReqNum()
                + "\n物料凭证请求处理成功数:" + result.getReqSucNum()
                + "\n物料凭证请求处理失败数:" + result.getReqFailNum()
                + "\n物料凭证请求明细数:" + result.getReqItemNum()
                ;
        //存结果
        saveResult(result,successList,failList,dayTime,endTimeStr);
        result.setMessage(message);
        return result;
    }

    private ExternalSyncMaterialLogsCheckResult checkMethod(List<MnSapIf004SDOForCheck> mnSapIf004SDOForChecks,Map<String, MaterialTypes> materialTypes,
                                                             List<MnSapIf004SDOForCheck> successList,List<MnSapIf004SDOForCheck> failList) {
        AtomicInteger reqSucNum = new AtomicInteger(0);
        AtomicInteger reqFailNum = new AtomicInteger(0);
        AtomicInteger reqItemNum = new AtomicInteger(0);
        List<String> outMaterialCertificateIdFailList = new ArrayList<>();
        List<Future<ExternalSyncMaterialLogsCheckResult>> futureList = Lists.newArrayList();
        ExternalSyncMaterialLogsCheckResult returnResult=new ExternalSyncMaterialLogsCheckResult();
        for (MnSapIf004SDOForCheck mainOMCOrder : mnSapIf004SDOForChecks) {
            CompletableFuture<ExternalSyncMaterialLogsCheckResult> future = CompletableFuture.supplyAsync(() -> {
                ExternalSyncMaterialLogsCheckResult tmpResult =new ExternalSyncMaterialLogsCheckResult();
                if (CollectionUtils.isNotEmpty(mainOMCOrder.getItems())) {
                    boolean hasErrorItem = false;
                    ArrayList<MnSapIf004DetailForCheck> subDetailList = new ArrayList<>();
                    for (MnSapIf004DetailForCheck subOMCOrder : mainOMCOrder.getItems()) {
                        //明细上标注一下物料凭证号，最后统计用
                        subOMCOrder.setScItemId(SapAndCrmFieldDealUtils.subStringScItmCode(subOMCOrder.getScItemId()));
                        subOMCOrder.setOutMaterialCertificateId(mainOMCOrder.getOutMaterialCertificateId());
                        if (!materialTypes.containsKey(subOMCOrder.getSapType() + subOMCOrder.getDcSignal())) {
                            //有移动类型不识别得直接算整条物料凭证有错
                            hasErrorItem = true;
                            break;
                        }
                        if (materialTypes.get(subOMCOrder.getSapType() + subOMCOrder.getDcSignal()).getInventoryType().intValue() == 1) {
                            //移动类型是在途得跳过不检查
                            continue;
                        }
                        subDetailList.add(subOMCOrder);
                    }
                    if (hasErrorItem) {
                        failList.add(mainOMCOrder);
                        tmpResult.setReqFailNum(1);
                    } else {
                        boolean checkItem = checkMaterialItem(subDetailList, mainOMCOrder.getOutMaterialCertificateId());
                        if (checkItem) {
                            tmpResult.setReqSucNum(1);
                            successList.add(mainOMCOrder);
                        } else {
                            tmpResult.setReqFailNum(1);
                            failList.add(mainOMCOrder);
                            tmpResult.setOutMaterialCertificateIdFailList
                                    (Arrays.asList(mainOMCOrder.getOutMaterialCertificateId()));
                        }
                        tmpResult.setReqItemNum(subDetailList.size());
                    }
                }
                //需要处理得物料凭证明细得条数累加
                return tmpResult;
            }, inventorySyncTaskExecutor).whenCompleteAsync((result,e)->{
                    if (CollectionUtils.isNotEmpty(result.getOutMaterialCertificateIdFailList())) {
                        outMaterialCertificateIdFailList.addAll(result.getOutMaterialCertificateIdFailList());
                    }
                    reqFailNum.getAndAdd(result.getReqFailNum() == null ? 0 : 1);
                    reqSucNum.getAndAdd(result.getReqSucNum() == null ? 0 : 1);
                    reqItemNum.getAndAdd(result.getReqItemNum()==null?0:result.getReqItemNum());
            });
            futureList.add(future);
        }
        try {
            CompletableFuture futureEnd=CompletableFuture.
                    allOf(futureList.toArray(new CompletableFuture[futureList.size()])).whenComplete((v,t)->{
            });
            futureEnd.get();
            returnResult.setReqItemNum(reqItemNum.get());
            returnResult.setReqFailNum(reqFailNum.get());
            returnResult.setReqSucNum(reqSucNum.get());
            returnResult.setOutMaterialCertificateIdFailList(outMaterialCertificateIdFailList);
        }catch (Exception e){
            log.error("物料凭证日志校验报错:"+e.toString());
        }
        return returnResult;
    }
// 老的方法 比较耗时。先保留 确定新的开线程方法稳定了再删除
//    private ExternalSyncMaterialLogsCheckResult checkMethod1(List<MnSapIf004SDOForCheck> mnSapIf004SDOForChecks,Map<String, MaterialTypes> materialTypes,
//         List<MnSapIf004SDOForCheck> successList,List<MnSapIf004SDOForCheck> failList) {
//        int reqSucNum = 0;
//        int reqFailNum = 0;
//        int reqItemNum = 0;
//        ExternalSyncMaterialLogsCheckResult result=new ExternalSyncMaterialLogsCheckResult();
//        List<String> outMaterialCertificateIdFailList = new ArrayList<>();
//        boolean hasErrorItem = false;
//        for (MnSapIf004SDOForCheck mainOMCOrder : mnSapIf004SDOForChecks) {
//            if (CollectionUtils.isNotEmpty(mainOMCOrder.getItems())) {
//                hasErrorItem = false;
//                ArrayList<MnSapIf004DetailForCheck> subDetailList = new ArrayList<>();
//                //遍历物料明证明细
//                for (MnSapIf004DetailForCheck subOMCOrder : mainOMCOrder.getItems()) {
//                    //明细上标注一下物料凭证号，最后统计用
//                    subOMCOrder.setScItemId(SapAndCrmFieldDealUtils.subStringScItmCode(subOMCOrder.getScItemId()));
//                    subOMCOrder.setOutMaterialCertificateId(mainOMCOrder.getOutMaterialCertificateId());
//                    if (!materialTypes.containsKey(subOMCOrder.getSapType() + subOMCOrder.getDcSignal())) {
//                        //有移动类型不识别得直接算整条物料凭证有错
//                        hasErrorItem = true;
//                        break;
//                    }
//                    if (materialTypes.get(subOMCOrder.getSapType() + subOMCOrder.getDcSignal()).getInventoryType().intValue() == 1) {
//                        //移动类型是在途得跳过不检查
//                        continue;
//                    }
//                    subDetailList.add(subOMCOrder);
//                }
//                if (hasErrorItem) {
//                    failList.add(mainOMCOrder);
//                    reqFailNum++;
//                } else {
//                    boolean checkItem = checkMaterialItem(subDetailList, mainOMCOrder.getOutMaterialCertificateId());
//                    if (checkItem) {
//                        reqSucNum++;
//                        successList.add(mainOMCOrder);
//                    } else {
//                        reqFailNum++;
//                        failList.add(mainOMCOrder);
//                        outMaterialCertificateIdFailList.add(mainOMCOrder.getOutMaterialCertificateId());
//                    }
//                }
//                //需要处理得物料凭证明细得条数累加
//                reqItemNum = reqItemNum + subDetailList.size();
//                subDetailList.clear();
//            }
//        }
//        result.setReqSucNum(reqSucNum);
//        result.setReqFailNum(reqFailNum);
//        result.setReqItemNum(reqItemNum);
//        if (CollectionUtils.isNotEmpty(outMaterialCertificateIdFailList)) {
//            result.setOutMaterialCertificateIdFailList(outMaterialCertificateIdFailList);
//        }
//        return result;
//    }

    private void saveResult(ExternalSyncMaterialLogsCheckResult result,List<MnSapIf004SDOForCheck> success,List<MnSapIf004SDOForCheck> fail,String startTime,String endTime){
        String reqId="MLC"+UUID.randomUUID().toString();
        LogsChecker logsChecker=new LogsChecker();
        logsChecker.setReqNum(result.getReqNum());
        logsChecker.setReqItemNum(result.getReqItemNum());
        logsChecker.setReqFialNum(result.getReqFailNum());
        logsChecker.setStartTime(startTime);
        logsChecker.setEndTime(endTime);
        logsChecker.setReqId(reqId);
        List<Future<Void>> futureList = Lists.newArrayList();
        success.stream().forEach(x-> {
            CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
                        MaterialLogsInfo logsInfo = new MaterialLogsInfo();
                        logsInfo.setReqId(reqId);
                        logsInfo.setStatus(1);
                        logsInfo.setOutMaterialCertificateId(x.getOutMaterialCertificateId());
                        logsInfo.setReqTime(x.getCreateTime());
                        createDetail(logsInfo);
                       return null;
                    }, inventorySyncTaskExecutor);
            futureList.add(future);
            }
        );
        fail.stream().forEach(x-> {
                    CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
                    MaterialLogsInfo logsInfo = new MaterialLogsInfo();
                    logsInfo.setReqId(reqId);
                    logsInfo.setStatus(-1);
                    logsInfo.setOutMaterialCertificateId(x.getOutMaterialCertificateId());
                    logsInfo.setReqTime(x.getCreateTime());
                    createDetail(logsInfo);
                        return null;
                   }, inventorySyncTaskExecutor);
                  futureList.add(future);
                }
        );
        CompletableFuture futureEnd=CompletableFuture.
                allOf(futureList.toArray(new CompletableFuture[futureList.size()])).whenComplete((v,t)->{
            create(logsChecker);
        });
        try {
            futureEnd.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    /**
     * 校验物料凭证明细 是否落库成功
     *
     * @param subDetailList
     * @param outMaterialCertificateId
     * @return
     */
    private boolean checkMaterialItem(ArrayList<MnSapIf004DetailForCheck> subDetailList, String outMaterialCertificateId) {
        if (CollectionUtils.isEmpty(subDetailList)) {
            return true;
        }
        Set<String> outMaterialCertificateIds = subDetailList.stream().map(x -> x.getOutMaterialCertificateId()).collect(Collectors.toSet());
        HashMap<String, List<OutInSubOrderDTO>> outInOrders = getOutInOrders(new ArrayList<>(outMaterialCertificateIds));
        List<String> outInOrdersForGetFlow = new ArrayList<>();
        for (List<OutInSubOrderDTO> tmp : outInOrders.values()) {
            tmp.forEach(x -> outInOrdersForGetFlow.add(x.getWmsSubOrderId()));
        }
        HashMap<String, List<BizInventoryFlowDTO>> flowOrders = getInventoryFlow(outInOrdersForGetFlow);
        //明细条数 和 出入库单条数相同 说明落库成功 出入库单判断完成
        int outInOrderSize = outInOrders.get(outMaterialCertificateId) == null ? 0 :
                outInOrders.get(outMaterialCertificateId).size();
        if (outInOrderSize != subDetailList.size()) {
            return false;
        }
        //判断库存流水
        List<OutInSubOrderDTO> outInOrdersForCheck = outInOrders.get(outMaterialCertificateId);
        for (OutInSubOrderDTO outInSubOrderDTO : outInOrdersForCheck) {
            List<BizInventoryFlowDTO> flowList = flowOrders.get(outInSubOrderDTO.getWmsSubOrderId());
            if (CollectionUtils.isEmpty(flowList)) {
                return false;
            }
            //流水按流行operation_type区分 求和数量
            // 预占可能存在数量误差 对不齐。
            HashMap<String, BigDecimal> quantityCheckMap = new HashMap<>();
            for (BizInventoryFlowDTO bizInventoryFlowDTO : flowList) {
                BigDecimal quantity = quantityCheckMap.get(bizInventoryFlowDTO.getOperationType());
                if (quantity == null) {
                    quantity = new BigDecimal(bizInventoryFlowDTO.getQuantity()).abs();
                } else {
                    BigDecimal nextQuantity = new BigDecimal(bizInventoryFlowDTO.getQuantity()).abs();
                    quantity = quantity.add(nextQuantity);
                }
                quantityCheckMap.put(bizInventoryFlowDTO.getOperationType(), quantity);
            }
            //校验数量
            for (BigDecimal mapItem : quantityCheckMap.values()) {
                BigDecimal outInquantity = new BigDecimal(outInSubOrderDTO.getGoodsQuantity());
                if (outInquantity.compareTo(mapItem) != 0) {
                    return false;

                }
            }
        }
        return true;
    }

    /**
     * 查库出入库但
     *
     * @param outMaterialCertificateIds
     * @return
     */
    private HashMap<String, List<OutInSubOrderDTO>> getOutInOrders(List<String> outMaterialCertificateIds) {
        HashMap<String, List<OutInSubOrderDTO>> mapResp = new HashMap<>();
        GetOutinOrdersWithoutfillinfoRequest request = new GetOutinOrdersWithoutfillinfoRequest();
        request.setOutSubOrderIds(outMaterialCertificateIds);
        Result<List<OutInSubOrderDTO>> resp = otsUnifiedInventoryService.getOutinOrdersWithoutfillinfo(request);
        List<OutInSubOrderDTO> outInSubOrderDTOList = resp.getResult();
        if (CollectionUtils.isNotEmpty(outInSubOrderDTOList)) {
            for (OutInSubOrderDTO tmp : outInSubOrderDTOList) {
                //物料凭证id做key
                List<OutInSubOrderDTO> subOrder = mapResp.get(tmp.getOutSubOrderId());
                if (CollectionUtils.isEmpty(subOrder)) {
                    subOrder = new ArrayList<>();
                }
                subOrder.add(tmp);
                mapResp.put(tmp.getOutSubOrderId(), subOrder);
            }
        }
        return mapResp;
    }

    /**
     * 从出入库但中捕获物料凭证id
     *
     * @param startTime
     * @param endTime
     * @return
     */
    private List<LogEntry> getOutMaterialIds(String startTime, String endTime) {
        LogStore logStore = Loader.loadSingle(LogStore.class);
        /**
         * @param logStore  集成日志 使用 api_adaptor_store
         * @param condition 查询条件，查询语法参考
         * {@link https://help.aliyun.com/document_detail/29060.htm?spm=a2c4g.11186623.0.0.4ff71edf8WwJ9C#concept-tnd-1jq-zdb}
         * @param topic 集成日志使用 api_adaptor
         * @param fromTime 查询开始时间
         * @param toTime 查询结束时间
         * @param line 查询的条目数量
         * @param offset 查询日志的偏移量
         * @return Pair
         * 		left : 总条目数量
         *      right : 日志明细
         */
        List<LogEntry> result=new ArrayList<>();
        String queryStr = "MnSapIf004$externalSyncMaterial";
        int offset=0;
        int line=100;
        for(int i=0;i<Integer.MAX_VALUE;i++) {
            Pair<Integer, List<LogEntry>> pair = logStore.find("api_adaptor_store", queryStr, "api_adaptor",
                    LocalDateTime.parse(startTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), LocalDateTime.parse(endTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), line, offset);
            offset=offset+line;
            if(CollectionUtils.isEmpty(pair.right())){
                break;
            }
            result.addAll(pair.right());
        }
        return result;
    }

    /**
     * 查库存流水
     *
     * @param subOutInOrderIds
     * @return
     */
    private HashMap<String, List<BizInventoryFlowDTO>> getInventoryFlow(List<String> subOutInOrderIds) {
        HashMap<String, List<BizInventoryFlowDTO>> map = new HashMap<>();
        GetBizinventoryFlowForCheckRequest getStockFlowListRequest = new GetBizinventoryFlowForCheckRequest();
        getStockFlowListRequest.setOutSubOrderIds(subOutInOrderIds);
        //1条出入库单对应的流水条数不确定，超过1000 流水查询会失败
        getStockFlowListRequest.setLimit(1000);
        getStockFlowListRequest.setStart(0);
        Result<List<BizInventoryFlowDTO>> result = otsUnifiedInventoryService.getBizinventoryFlowForCheck(getStockFlowListRequest);
        /**
         * 同一个出入库单可能有多条流水
         */
        if (result.getResult() != null && CollectionUtils.isNotEmpty(result.getResult())) {
            result.getResult().forEach(x -> {
                String outInOrderId = x.getOutSubOrderId();
                List<BizInventoryFlowDTO> list = map.get(outInOrderId);
                if (CollectionUtils.isEmpty(list)) {
                    list = new ArrayList<>();
                }
                list.add(x);
                map.put(outInOrderId, list);
            });
        }

        return map;
    }

    /**
     * 将日志里的物料凭证请求去重 返回list
     *
     * @return
     */
    private List<MnSapIf004SDOForCheck> getSapIf004DOListAfterFilter(List<LogEntry> logEntries,
                                                                    HashMap<String,String> retryOutMaterialIds) {
        List<MnSapIf004SDOForCheck> result = new ArrayList<>();
        Set<String> materialIds = new HashSet<>();
        if (CollectionUtils.isNotEmpty(logEntries)) {
            // 转成需要的对象 然后自己过滤物料凭证id logEntries.get(0).toMap().get("input")
            for (LogEntry logEntry : logEntries) {
                Map<String,String> logEntryMap= logEntry.toMap();
                String materialJson =logEntryMap.get("input");
                try {
                    MnSapIf004DOForCheckWrapper materialWrapper =  new Gson().fromJson(materialJson,MnSapIf004DOForCheckWrapper.class);
                    if (materialWrapper.getBody() != null && CollectionUtils.isNotEmpty(materialWrapper.getBody().getData())) {
                        MnSapIf004SDOForCheck material = materialWrapper.getBody().getData().get(0);
                        //放到map里 重试用
                        retryOutMaterialIds.put(material.getOutMaterialCertificateId(), new Gson().toJson(materialWrapper.getBody()));
                        if (materialIds.contains(material.getOutMaterialCertificateId())) {
                            continue;
                        }
                        material.setCreateTime(logEntryMap.get("created"));
                        materialIds.add(material.getOutMaterialCertificateId());
                        result.add(material);
                    }
                } catch (Exception e) {

                }

            }
        }
        return result;
    }



}
